/*
    This is a set of libraries to assist in OpenGl and Other Applications
    Copyright (C) 2008  The Modular Engine Team

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#define WINDOWMODULE
#include "WindowModule.h"
#define DEFCLASSNAME "defaultClassName" // default szClassName for window

#include <iostream>
using namespace std;

namespace ModularEngine
{

	#if WINDOWS
	int WindowModule::initialize()
	{
		// Starts up a window in proper fashion.

		// Setup the window
		memset( &wcx, 0, sizeof( WNDCLASSEX ) );
		wcx.cbSize = sizeof( WNDCLASSEX );
		wcx.hInstance = 0;
		wcx.lpszClassName = mpClassName;
		wcx.lpfnWndProc = StaticWndProc;
		wcx.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
		wcx.lpszMenuName = 0;
		wcx.cbClsExtra = 0;
		wcx.cbWndExtra = 0;
		wcx.hIcon = LoadIcon( NULL, IDI_APPLICATION );
		wcx.hIconSm = LoadIcon( NULL, IDI_APPLICATION );
		wcx.hCursor = LoadCursor( NULL, IDC_ARROW );
		wcx.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
		if( !RegisterClassEx( &wcx ) )
		{
			THROWERROR( "Unable to Register Class" );
			return 0;
		}

		setMouse( IDC_ARROW );

		// Create the window
		mhWnd = CreateWindowA( mpClassName, mpWindowName, WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_BORDER, mX, mY, mWidth, mHeight, 0, 0, 0, 0 );

 
		#ifndef HID_USAGE_PAGE_GENERIC
		#define HID_USAGE_PAGE_GENERIC ((USHORT) 0x01)
		#endif
		#ifndef HID_USAGE_GENERIC_MOUSE
		#define HID_USAGE_GENERIC_MOUSE ((USHORT) 0x02)
		#endif
		#ifndef HID_USAGE_GENERIC_KEYBOARD
		#define HID_USAGE_GENERIC_KEYBOARD ((USHORT) 0x06)
		#endif

		RAWINPUTDEVICE Rid[2];
		Rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC; 
		Rid[0].usUsage = HID_USAGE_GENERIC_MOUSE; 
		Rid[0].dwFlags = RIDEV_INPUTSINK;   
		Rid[0].hwndTarget = mhWnd;
		Rid[1].usUsagePage = HID_USAGE_PAGE_GENERIC;
		Rid[1].usUsage = HID_USAGE_GENERIC_KEYBOARD;
		Rid[1].dwFlags = RIDEV_INPUTSINK;
		Rid[1].hwndTarget = mhWnd;
		RegisterRawInputDevices(Rid, 2, sizeof(Rid[0]));


		return 1;
	}

	int WindowModule::run()
	{
		// Runs the message loop, etc.
		if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
		{
			if( msg.message == WM_QUIT )
			{
				// If the window's been closed, it's probably best to stop the engine.
				ModularEngine::EngineCore::getSingleton().stop();
			}
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}
		return 1;
	}


	WindowModule::WindowModule()
	{
		// Set names, dimension's etc. to their default values. 
		mpClassName = new char[strlen(DEFCLASSNAME)+1];
		strcpy( mpClassName, DEFCLASSNAME );
		mpWindowName = new char[strlen(DEFCLASSNAME)+1];
		strcpy( mpWindowName, DEFCLASSNAME );
		mX = mY = 0;
		mWidth = 800;
		mHeight = 600;
	}

	WindowModule::~WindowModule()
	{
		// Cleanup, Cleanup, everybody everywhere
		mhWnd = 0;
		delete [] mpClassName;
		delete [] mpWindowName;
	}

	LRESULT CALLBACK WindowModule::StaticWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
	{
		// Can't have a dynamic wndProc so here's the hack to make it static... or not.
		return WindowModule::getSingleton().WndProc( hWnd, uMsg, wParam, lParam );		
	}

	WORD KeyMsgToAscII(WPARAM wparam, LPARAM lparam)
	{
		 BYTE  keyState[256];
		 GetKeyboardState(keyState);
		 HKL   hkl = LoadKeyboardLayout("00000409", KLF_ACTIVATE);


		 struct KeyParam
		 {
			   unsigned repeat :15;
			   unsigned scan : 8;
			   unsigned rest :9;
		 };

		 KeyParam kp;
		 memcpy(&kp, &lparam, sizeof(LPARAM));
	   
		 WORD c;
		 ToAsciiEx(wparam, kp.scan, keyState, &c, 0, hkl);
		  return c;
	}

	LRESULT WindowModule::WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
	{


		switch( uMsg )
		{
#if USING_DEPRECATED_INPUT
		case WM_LBUTTONDOWN:
			// 0 is HUD_LEFT_DOWN
			mbLButtonDown = true;
			mouseSend( 0 );
			return 0;
		case WM_MBUTTONDOWN:
			// 1 is HUD_MIDDLE_DOWN
			mbMButtonDown = true;
			mouseSend( 1 );
			return 0;
		case WM_RBUTTONDOWN:
			// 2 is HUD_RIGHT_DOWN
			mbRButtonDown = true;
			mouseSend( 2 );
			return 0;
		case WM_LBUTTONUP:
			mbLButtonDown = false;
			return 0;
		case WM_MBUTTONUP:
			mbMButtonDown = false;
			return 0;
		case WM_RBUTTONUP:
			mbRButtonDown = false;
			return 0;
		case WM_KEYDOWN:
			// Close the window if user hits escape.
			switch( wParam )
			{
			case VK_SHIFT:			
			case VK_CAPITAL:
				break;
			case VK_F1:
				keyboardSend( 9 );
				break;
			case VK_TAB:
				keyboardSend( 7 );
				break;
			case VK_ESCAPE:
				keyboardSend( 6 );
				break;
			case VK_DOWN:
				keyboardSend( 5 );
				break;
			case VK_UP:
				keyboardSend( 4 );					
				break;
			case VK_LEFT:
				keyboardSend( 12 );
				break;
			case VK_RIGHT:
				keyboardSend( 13 );
				break;
			case VK_DELETE:
				keyboardSend( 0 );
				break;
			case VK_HOME:
				keyboardSend( 1 );
				break;
			case VK_END:
				keyboardSend( 2 );
				break;
			case VK_RETURN:
				keyboardSend( 3 );
				break;
			default:
				keyboardSend( KeyMsgToAscII( wParam, lParam ) );
			}	
			return 0;
		case WM_MOUSEMOVE:
			SetCursor( LoadCursor( NULL, msMouseName ) );
			return 0;
#endif // #if USING_DEPRECATED_INPUT

		case WM_INPUT:
			{
				
				switch( wParam )
				{
				case RIM_INPUT: // Input from foreground
					{

					} break;
				case RIM_INPUTSINK: // Input from background
					{

					} break;
				}

				UINT dwSize = 40;
				static BYTE lpb[40];
		    
				GetRawInputData((HRAWINPUT)lParam, RID_INPUT, 
								lpb, &dwSize, sizeof(RAWINPUTHEADER));
		    
				RAWINPUT* raw = (RAWINPUT*)lpb;

				switch( raw->header.dwType )
				{
				case RIM_TYPEKEYBOARD:
					{

						unsigned char state [256];
						GetKeyboardState( state );
						//InputModule::getSingleton().passKeyboardState( state );
						char charValue[2];
						if( raw->data.keyboard.Message == WM_KEYDOWN )
						{
							if( ToAsciiEx( raw->data.keyboard.VKey, raw->data.keyboard.MakeCode, state, (LPWORD)&charValue[0], 0, GetKeyboardLayout(0) ) && !(GetKeyState( VK_CONTROL ) & 0x800) )
							{
							
								// For typing
								InputModule::getSingleton().addKeyTyped( charValue[0] );
							//	cout << charValue[0] << endl;
							}
						}
						else if( raw->data.keyboard.Message == WM_SYSKEYDOWN || raw->data.keyboard.Message == WM_SYSKEYUP )
						{
							cout << "SYS" << endl;
						}
					} break;
				case RIM_TYPEMOUSE:
					{
					//	cout << "Mouse" << endl;
					} break;
				}

				DefWindowProc( hWnd, uMsg, wParam, lParam );
			} break;
		case WM_DESTROY:
			EngineCore::getSingleton().stop();
			return 0;
		default:
			return DefWindowProc( hWnd, uMsg, wParam, lParam );
		}
		return 1;
	}

	// TODO: Separate this from this file so that HUDModule
	// and other modules aren't prerequisites.
	// I suppose it doesn't really matter.
	void WindowModule::mouseSend(UINT down)
	{		
		if( mouseSendF )
			(*mouseSendF)( down );
	}

	void WindowModule::keyboardSend( UINT nKey )
	{
		if( keyboardSendF )
			(*keyboardSendF)( nKey );
	}

	Vector2d WindowModule::getMouse()
	{
		// Returns cursor relative to the window.
		POINT cPos;
		GetCursorPos( &cPos );
		RECT wRect;
		GetWindowRect( mhWnd, &wRect );

		return Vector2d( cPos.x-wRect.left-4, cPos.y-wRect.top-30 );
	}

	bool WindowModule::isDown( UINT uMouse )
	{
		switch( uMouse )
		{
		case 0:
			return mbLButtonDown;
		case 1:
			return mbMButtonDown;
		case 2:
			return mbRButtonDown;
		default:
			return false;
		}
			
	}
	#endif WINDOWS

}
